#!/usr/bin/python2.7
#
# dedupv1 - iSCSI based Deduplication System for Linux
#
# (C) 2008 Dirk Meister
# (C) 2009 - 2011, Dirk Meister, Paderborn Center for Parallel Computing
# (C) 2012 Dirk Meister, Johannes Gutenberg University Mainz
#
# This file is part of dedupv1.
#
# dedupv1 is free software: you can redistribute it and/or modify it under the terms of the
# GNU General Public License as published by the Free Software Foundation, either version 3
# of the License, or (at your option) any later version.
#
# dedupv1 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
# even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with dedupv1. If not, see http://www.gnu.org/licenses/.
#

import sys
import os
import optparse
import json

if "DEDUPV1_ROOT" not in os.environ:
    DEDUPV1_ROOT= os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]),"../"))
    os.environ["DEDUPV1_ROOT"] = DEDUPV1_ROOT

sys.path.insert(0, os.path.normpath(os.path.join(DEDUPV1_ROOT, "lib/python")))
for file in [f for f in os.listdir(os.path.join(DEDUPV1_ROOT, "lib/python2.7/site-packages/")) if f.endswith(".egg")]:
    sys.path.insert(0, os.path.normpath(os.path.join(DEDUPV1_ROOT, "lib/python2.7/site-packages/", file)))
import dedupv1
import cmd
import scst
from omdict import omdict
from monitor import Monitor
import iscsi_scst
import group
import volume
from dedupv1logging import log_error, log_info, log_warning, log_verbose
import config
from dedupv1_util import parse_params

monitor = None

def print_raw_data(data):
    print json.dumps(data, sort_keys=True, indent=4)

def add_initiator(options, args):
    config_params = omdict( [ ("op", "addinitiator") ] )
    config_params = parse_params(config_params, args)
    if not "name" in config_params:
        raise Exception("Missing required option: name");
    if not "initiator" in config_params:
        raise Exception("Missing required option: initiator");
    if len(config_params.get_multi("initiator")) != 1:
        raise Exception("Illegal option: initiator");

    group_name = config_params["name"]
    g = group.Group(group_name, config_params)
    validation_errors = g.validate()
    if len(validation_errors) > 0:
        raise Exception("Illegal group: " + ",".join(validation_errors))

    for initiator_pattern in config_params.get_multi("initiator"):
        if initiator_pattern in g.initiator_pattern():
            if options.force:
                log_warning(options, "Initiator pattern already existing")
            else:
                raise Exception("Initiator pattern already existing")

    monitor_data = monitor.read("group", config_params.items())
    if "ERROR" in monitor_data:
        raise Exception(monitor_data["ERROR"])
    else:
        if options.raw:
            print_raw_data(monitor_data)

        for initiator_pattern in config_params.get_multi("initiator"):
            scst.add_initiator_pattern_to_group(
                initiator_pattern,
                group_name)

def rm_initiator(options, args):
    config_params = omdict( [ ("op", "rminitiator") ] )
    config_params = parse_params(config_params, args)
    if not "name" in config_params:
        raise Exception("Missing required option: name");
    if not "initiator" in config_params:
        raise Exception("Missing required option: initiator");
    if len(config_params.get_multi("initiator")) != 1:
        raise Exception("Illegal option: initiator");

    group_name = config_params["name"]
    g = group.read_group(monitor, group_name)
    validation_errors = g.validate()
    if len(validation_errors) > 0:
        raise Exception("Illegal group: " + ",".join(validation_errors))

    for initiator_pattern in config_params.get_multi("initiator"):
        if not initiator_pattern in g.initiator_pattern():
            if options.force:
                log_warning(options, "Initiator pattern not existing")
            else:
                raise Exception("Initiator pattern not existing")

    monitor_data = monitor.read("group", config_params.items())
    if "ERROR" in monitor_data:
        raise Exception(monitor_data["ERROR"])
    else:
        if options.raw:
            print_raw_data(monitor_data)
        for initiator_pattern in config_params.get_multi("initiator"):
            scst.rm_initiator_pattern_from_group(initiator_pattern, group_name)

def add_group(options, args):
    config_params = omdict( [ ("op", "add") ] )
    config_params = parse_params(config_params, args)

    if not "name" in config_params:
        raise Exception("Missing required option: name")
    group.check_group_name(config_params["name"])

    g = group.Group(config_params["name"], config_params)
    validation_errors = g.validate()
    if len(validation_errors) > 0:
        raise Exception("Illegal group: " + ",".join(validation_errors))

    monitor_data = monitor.read("group", config_params.items());
    if "ERROR" in monitor_data:
        raise Exception(monitor_data["ERROR"])
    else:
        if options.raw:
            print_raw_data(monitor_data)

        scst.add_group(g.name)
        log_verbose(options, "Group %s registered" % g.name)

def remove_group(options, args):
    config_params = omdict( [ ("op", "remove") ] )
    config_params = parse_params(config_params, args)
    if not "name" in config_params:
        raise Exception("Missing required option: name");

    g = group.read_group(monitor, config_params["name"])
    validation_errors = g.validate()
    if len(validation_errors) > 0:
        raise Exception("Illegal group: " << ",".join(validation_errors))

    if len(scst.get_group_session(g.name)) > 0:
        raise Exception("Unable to remove group with active sessions")

    # check that no volume is in the target
    vols = volume.read_all_volumes(monitor)
    for (vol_id, vol) in vols.items():
        if not vol:
            continue
        for (group_name, lun) in vol.groups():
            if group_name == g.name:
                if options.force:
                    log_warning(options, "Group %s has still assigned volumes" % (g.name))
                else:
                    raise Exception("Group %s has still assigned volumes" % (g.name))

    monitor_data = monitor.read("group", config_params.items());
    if "ERROR" in monitor_data:
        raise Exception(monitor_data["ERROR"])
    else:
        if options.raw:
            print_raw_data(monitor_data)
        scst.rm_group(g.name)
        log_verbose(options, "Group %s unregistered" % g.name)

def show_groups(options):
    if options.raw:
        print_raw_data(monitor.read("group"))
    else:
        groups = group.read_all_groups(monitor)
        for name in sorted(groups.keys()):
            g = groups[name]
            print "Group: %s" % name
            print "Initiator pattern: %s" % (", ".join(g.initiator_pattern()))
            print "Volumes: %s" % (", ".join(g.volumes()))
            print

if __name__ == "__main__":
    if not (dedupv1.check_root() or dedupv1.check_dedupv1_group()):
        print sys.stderr, "Permission denied"
        sys.exit(1)

    usage = """usage: %prog (add | remove | initiator add | initiator remove | show) [options]

Examples:
%prog add name=backups
%prog remove name=backups
%prog initiator add name=backups initiator=21:*:e0:?b:83:*
%prog initiator remove name=backups initiator=21:*:e0:?b:83:*
%prog show
%prog --version
%prog --help
"""
    version = "%s (hash %s)" % (config.DEDUPV1_VERSION_STR, config.DEDUPV1_REVISION_STR)

    parser = optparse.OptionParser(usage=usage, version=version)

    parser.add_option("-p","--port", type="int", dest="port", help="port of the dedupv1d", default=config.DEDUPV1_DEFAULT_MONITOR_PORT)
    parser.add_option("--host", dest="hostname", help="hostname of the dedupv1d", default="localhost")
    parser.add_option("--raw", dest="raw", action="store_true", help="outputs in raw JSON format", default=False)
    parser.add_option("-f", "--force",
        action="store_true",
        dest="force",
        default=False)
    parser.add_option("-v", "--verbose",
        dest="verbose",
        action="store_true",
        default=False)
    parser.add_option("--debug",
        dest="debug",
        action="store_true",
        default=False)
    (options, args) = parser.parse_args()

    monitor = Monitor(options.hostname, options.port);

    if len(args) == 0:
        parser.error("No command specified")
        sys.exit(1)
    cmd = args[0]
    try:
        if cmd == "add":
            add_group(options, args[1:])
        elif cmd == "remove":
            remove_group(options, args[1:])
        elif cmd == "initiator":
            if len(args) == 1:
                parser.error("No sub command for command %s" % cmd)
            subcmd = args[1]
            if subcmd == "add":
                add_initiator(options, args[2:])
            elif subcmd == "remove":
                rm_initiator(options, args[2:])
            else:
                parser.error("Illegal subcommand for command %s" % cmd)
        elif cmd == "show":
            show_groups(options)
        else:
            parser.error("Invalid command %s" % cmd)
            sys.exit(1)
    except KeyboardInterrupt:
        sys.exit(1)
    except Exception as e:
        log_error(options, e)
        sys.exit(1)
    sys.exit(0)
